package com.okhttp.retrofit;

import android.support.v4.util.ArrayMap;
import android.support.v4.util.SimpleArrayMap;
import com.okhttp.interceptor.HeaderInterceptor;
import com.okhttp.interceptor.NetInterceptor;
import com.okhttp.retrofit.callfactory.CallFactoryProxy;
import com.okhttp.retrofit.mock.MockHook;
import com.okhttp.utils.IRetrofitLog;
import com.okhttp.utils.RetrofitLog;
import com.okhttp.utils.Utils;
import java.lang.ref.WeakReference;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.List;
import java.util.Map.Entry;
import java.util.concurrent.TimeUnit;
import okhttp3.Interceptor;
import okhttp3.OkHttpClient;
import okhttp3.internal.Util;
import okhttp3.logging.HttpLoggingInterceptor;
import okhttp3.logging.HttpLoggingInterceptor.Level;
import okhttp3.logging.HttpLoggingInterceptor.Logger;
import retrofit2.Call;
import retrofit2.CallAdapter;
import retrofit2.Converter;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;

/**
 * @author GYL
 * @dateTime 2020/4/21-14:13
 * @description Retrofit client
 */
public class RetrofitClient {
  
  private static volatile RetrofitClient instance;

  //private Retrofit retrofit;
  private ArrayMap<String,Retrofit>retrofits;
  private Object retrofitService;

  private Config mConfig;

  private boolean isDebug;

  private boolean isMock;
  
  private ArrayMap<Integer, WeakReference<Call>> callMaps;
  
  public synchronized static RetrofitClient getInstance() {
    if (instance == null) {
      synchronized (RetrofitClient.class) {
        if (instance == null) {
          instance = new RetrofitClient();
        }
      }
    }
    return instance;
  }
  
  private RetrofitClient() {
    callMaps = new ArrayMap<>();
    retrofits=new ArrayMap<>();
  }
  
  public boolean isDebug() {
    return this.isDebug;
  }
  
  public void setRetrofitConfig(Config config) {
    this.mConfig = config;
    this.isDebug = config.isDebug;
    this.isMock= config.isMock;
    if (this.isDebug) {
      RetrofitLog.setIRetrofitLog(config.mRetrofitLog);
    }
  }

  private Retrofit createRetrofit(String hostUrl) {
    //创建Retrofit
    Retrofit.Builder retrofitBuilder = new Retrofit.Builder();
    //添加Converter,这里按顺序执行，需要先添自定义的converter
    boolean isHasGsonConverterFactory = false;
    if (mConfig.converterFactoryList != null) {
      for (Converter.Factory factory : mConfig.converterFactoryList) {
        retrofitBuilder.addConverterFactory(factory);
        if (factory instanceof GsonConverterFactory) {
          //已经包含GSONConverterFactory
          isHasGsonConverterFactory = true;
        }
      }
    }
    //默认添加GSON数据转换器
    if (!isHasGsonConverterFactory) {
      //不包含GSONConverterFactory
      retrofitBuilder.addConverterFactory(GsonConverterFactory.create());
    }
    if (mConfig.mCallAdpaterFactory != null) {
      retrofitBuilder.addCallAdapterFactory(mConfig.mCallAdpaterFactory);
    }
    //创建OkHttpClient
    OkHttpClient okHttpClient = createOkHttpClient();
    //设置Call.Factory
    if (mConfig.mCallFactory != null) {
      mConfig.mCallFactory.setCallFactory(okHttpClient);
      retrofitBuilder.callFactory(mConfig.mCallFactory);
    } else {
      retrofitBuilder.client(okHttpClient);
    }
    retrofitBuilder.baseUrl(hostUrl);
    return retrofitBuilder.build();
  }

  /**
   * 获取 Retrofit config.hostUrl
   * @see #getRetrofit(String)
   * @return
   */
  public Retrofit getRetrofit() {
    //获取Config#Hosturl的Retrofit
    return retrofits.get(mConfig.hostUrl);
  }

  /**
   * 获取 Retrofit
   * @param hostUrl
   * @return
   */
  public Retrofit getRetrofit(String hostUrl){
    //根据HostUrl获取Retrofit
    Retrofit retrofit = this.retrofits.get(hostUrl);
    if(retrofit==null){
      retrofit=createRetrofit(hostUrl);
      retrofits.put(hostUrl,retrofit);
    }
    return retrofit;
  }
  
  private OkHttpClient createOkHttpClient() {
    //OKhttp client
    OkHttpClient.Builder okBuilder = new OkHttpClient.Builder();
    //添加Header,HeaderInterceptor 不库为空时，HeaderMap无效
    if (mConfig.mHeaderInterceptor != null) {
      okBuilder.addInterceptor(mConfig.mHeaderInterceptor);
    } else {
      if (mConfig.headers != null) {
        okBuilder.addInterceptor(new HeaderInterceptor(mConfig.headers));
      }
    }
    //默认使用CacheInterceptor
    if (mConfig.mCacheInterceptor != null) {
      okBuilder.addInterceptor(mConfig.mCacheInterceptor);
    }
    //网络拦截器
    if (mConfig.netInterceptor != null) {
      okBuilder.addNetworkInterceptor(mConfig.netInterceptor);
    }
    //
    if (mConfig.interceptorList != null && mConfig.interceptorList.size() > 0) {
      for (Interceptor interceptor : mConfig.interceptorList) {
        okBuilder.addInterceptor(interceptor);
      }
    }
    //调试模式下打印日志,再其他Interceptor之后添加。
    if (isDebug) {
      HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor(new Logger() {
        @Override
        public void log(String message) {
          try {
            RetrofitLog.d("Retrofit", URLDecoder.decode(message, "UTF-8"));
          } catch (Exception e) {
            RetrofitLog.d("Retrofit", message);
          }
        }
      });
      loggingInterceptor.setLevel(mConfig.isDebug ? Level.BODY : Level.NONE);
      okBuilder.addInterceptor(loggingInterceptor);
    }
    //失败重连
    okBuilder.retryOnConnectionFailure(true)
      //超时时间
      .connectTimeout(30, TimeUnit.SECONDS)
      .writeTimeout(10, TimeUnit.SECONDS)
      .readTimeout(10, TimeUnit.SECONDS);

    OkHttpClient client = okBuilder.build();
    return client;
  }
  
  /**
   * 创建Retrofit 服务接口
   * @see #createService(String, Class)
   */
  @Deprecated
  public <T> T createService(Class<T> service) {
    Retrofit retrofit=this.getRetrofit();
    //相同的Service 不需要重建
    if (retrofitService == null || !service.isAssignableFrom(retrofitService.getClass())) {
      if(isMock){
        retrofitService= MockHook.createService(retrofit,service);
      }else{
        retrofitService = retrofit.create(service);
      }
      //return (T) retrofitService;
    }
    return (T) retrofitService;
  }

  /**
   * 创建Retrofit 服务接口
   * @param hostUrl 当前服务器地址
   * @param service 当前服务接口
   * @param <T>
   * @return
   */
  public <T> T createService (String hostUrl,Class<T>service){
    Retrofit retrofit=this.getRetrofit(hostUrl);
    //相同的Service 不需要重建
    if (retrofitService == null || !service.isAssignableFrom(retrofitService.getClass())) {
      //return (T) retrofitService;
      if(isMock){
        retrofitService= MockHook.createService(retrofit,service);
      }else{
        retrofitService = retrofit.create(service);
      }
    }
    return (T) retrofitService;
  }
  
  public void addRetrofitCall(Integer key, Call call) {
    if (key != null && call != null) {
      this.callMaps.put(key, new WeakReference<Call>(call));
    }
  }
  
  protected synchronized void cancel(final int what) {
    RetrofitLog.e("RetrofitClient", "[RetrofitClient.cancel]：cancel call.(what=" + what + ")");
    if (this.callMaps.containsKey(what)) {
      WeakReference<Call> weakReference = this.callMaps.get(what);
      if (weakReference != null) {
        Call call = weakReference.get();
        if (call != null && !call.isCanceled()) {
          call.cancel();
        }
        weakReference.clear();
      }
      this.callMaps.remove(what);
    }
  }
  
  public synchronized void delRetrofitCall(Integer... keys) {
    if (keys != null) {
      for (Integer key : keys) {
        if (this.callMaps.containsKey(key)) {
          WeakReference<Call> weakReference = this.callMaps.get(key);
          if (weakReference != null) {
            Call call = weakReference.get();
            if (call != null && !call.isCanceled()) {
              call.cancel();
            }
            weakReference.clear();
          }
          this.callMaps.remove(key);
        }
      }
    }
  }
  
  public synchronized void delAllRetroiftCalls() {
    if (this.callMaps.size() > 0) {
      for (Entry<Integer, WeakReference<Call>> entry : callMaps.entrySet()) {
        WeakReference<Call> value = entry.getValue();
        if (value != null) {
          Call call = value.get();
          if (call != null && !call.isCanceled()) {
            call.cancel();
          }
          value.clear();
        }
      }
      this.callMaps.clear();
    }
  }
  
  public Call getRetrofitCall(Integer key) {
    WeakReference<Call> weakReference = this.callMaps.get(key);
    if (weakReference != null) {
      return weakReference.get();
    }
    return null;
  }
  
  public static class Config {
    
    /**
     * 服务器URL,必须以“/”结尾
     */
    private String hostUrl;
    /**
     * 是否打打包输出
     */
    private boolean isDebug;

    //是否使用Mock数据
    private boolean isMock;

    //打印设置
    private IRetrofitLog mRetrofitLog;
    /**
     * 数据转换器：GSON，String
     */
    private List<Converter.Factory> converterFactoryList;
    
    /**
     * 拦截器列表
     */
    private List<Interceptor> interceptorList;
    /**
     * 缓存拦截器
     */
    private Interceptor mCacheInterceptor;
    
    /**
     * 请求Header 拦截器，不为空时， Map Header无效
     */
    private Interceptor mHeaderInterceptor;
    
    private NetInterceptor netInterceptor;
    
    /**
     * Header 实现方法
     */
    private ArrayMap<String, String> headers;
    
    private CallFactoryProxy mCallFactory;
    
    private CallAdapter.Factory mCallAdpaterFactory;
    
    public Config() {
    }

    /**
     * 服务器URL集合,url必须以“/”结尾
     * @param url
     * @return
     */
    public Config withHostUrl(String url) {
      this.hostUrl = url;
      return this;
    }
    
    public Config withDebug(boolean isDebug) {
      this.isDebug = isDebug;
      return this;
    }
    
    public Config setRetrofitLog(IRetrofitLog retrofitLog) {
      this.mRetrofitLog = retrofitLog;
      return this;
    }

    /**
     * 是否使用Mock
     * @param isMock true 使用，false 不使用
     * @return
     */
    public Config withMock(boolean isMock){
      this.isMock=isMock;
      return this;
    }

    /**
     * 添加数据解析器，按添加顺序解析
     */
    public Config addConverterFactory(Converter.Factory factory) {
      if (converterFactoryList == null) {
        converterFactoryList = new ArrayList<>();
      }
      if(!converterFactoryList.contains(factory)){
        converterFactoryList.add(factory);
      }
      return this;
    }
    
    public Config addCallAdapterFactory(CallAdapter.Factory callAdpaterFactory) {
      this.mCallAdpaterFactory = callAdpaterFactory;
      return this;
    }
    
    public Config witlisthConverterFactories(List<Converter.Factory> converterFactories) {
      if (converterFactoryList != null) {
        this.converterFactoryList.addAll(converterFactories);
      } else {
        this.converterFactoryList = converterFactories;
      }
      return this;
    }
    
    public Config withInterceptorList(List<Interceptor> interceptorList) {
      if (this.interceptorList != null) {
        this.interceptorList.addAll(interceptorList);
      } else {
        this.interceptorList = interceptorList;
      }
      return this;
    }
    
    public Config addInterceptor(Interceptor interceptor) {
      if (interceptorList == null) {
        interceptorList = new ArrayList<>();
      }
      interceptorList.add(interceptor);
      return this;
    }
    
    public Config addNetInterceptor(NetInterceptor netInterceptor) {
      this.netInterceptor = netInterceptor;
      return this;
    }
    
    public Config withCacheInterceptor(Interceptor cacheInterceptor) {
      this.mCacheInterceptor = cacheInterceptor;
      return this;
    }
    
    public Config withHeaderInterceptor(Interceptor headerInterceptor) {
      this.mHeaderInterceptor = headerInterceptor;
      return this;
    }
    
    public Config withCallFactory(CallFactoryProxy callFactory) {
      this.mCallFactory = callFactory;
      return this;
    }
    
    public Config withHeaders(ArrayMap<String, String> headers) {
      if (headers != null) {
        this.headers.putAll((SimpleArrayMap<? extends String, ? extends String>) headers);
      } else {
        this.headers = headers;
      }
      return this;
    }
    
    public Config addHeader(String key, String value) {
      if (headers == null) {
        headers = new ArrayMap<>();
      }
      headers.put(key, value);
      return this;
    }
    
  }
  
}